use std::net::SocketAddr;
use tokio::io::copy_bidirectional_with_sizes;
use tokio::net::{TcpListener, TcpStream};
use tokio::runtime::*;

fn main() {
    let opts = hiopt::options!["addr:", "a:", "threads:", "t:", "buffer:", "b:", "help", "h"];

    let args = unsafe { hiopt::raw_args_from_i8(hictor::args()) };

    let mut listening_addr = None;
    let mut server_addr = None;
    let mut nth = 0_usize;
    let mut buflen = 1024_usize;
    // ignore program name
    for opt in opts.opt_iter(&args[1..]) {
        let (opt_idx, opt_value) = opt.unwrap();
        match opt_idx {
            0..=1 => listening_addr = Some(format!("127.0.0.1:{}", opt_value.unwrap())),
            2..=3 => nth = opt_value.unwrap().parse().unwrap(),
            4..=5 => buflen = opt_value.unwrap().parse().unwrap(),
            6..=7 => return help(),
            _ => unreachable!(),
        }
    }

    for addr in opts.noopt_iter(&args[1..]) {
        server_addr = Some(format!("127.0.0.1:{addr}"));
        break;
    }

    if listening_addr.is_none() || server_addr.is_none() {
        return help();
    }

    let rt = if nth == 0 {
        Builder::new_multi_thread().enable_all().build().unwrap()
    } else {
        Builder::new_multi_thread()
            .enable_all()
            .worker_threads(nth)
            .build()
            .unwrap()
    };
    let _ = rt.block_on(listening(
        (&listening_addr.unwrap()).parse().unwrap(),
        (&server_addr.unwrap()).parse().unwrap(),
        buflen,
    ));
}

fn help() {
    println!(
        "Usage: {} options server_addr",
        hictor::program_invocation_name()
    );
    println!("server_addr: port");
    println!("options:");
    println!("--addr | -a: port");
    println!("--threads | -t: defaut is 0");
    println!("--buffer | -b: buflen, defaut is 1024");
    println!("--help | -h: print help message");
}

async fn listening(addr: SocketAddr, remote: SocketAddr, buflen: usize) {
    let Ok(listener) = TcpListener::bind(addr).await else {
        println!("listen fail: {addr}");
        return;
    };

    while let Ok((mut inbound, _)) = listener.accept().await {
        let addr = remote.clone();
        tokio::spawn(async move {
            let Ok(mut outbound) = TcpStream::connect(addr).await else {
                return;
            };
            let _ =
                copy_bidirectional_with_sizes(&mut inbound, &mut outbound, buflen, buflen).await;
        });
    }
}
